I'm thinking about creating ostree-prepare-soft-reboot.c.
Prepare for this by factoring out shared helper functions.
src/libotcore/otcore-spki-verify.c \
$(NULL)
-libotcore_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_CRYPTO_LIBS) $(LIBSYSTEMD_CFLAGS)
+# Note that this also uses *includes* from libostree, so there's a partial
+# circular dependency.
+libotcore_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -DLOCALEDIR=\"$(datadir)/locale\" $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_CRYPTO_LIBS) $(LIBSYSTEMD_CFLAGS)
libotcore_la_LIBADD = $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) $(LIBSYSTEMD_LIBS) $(OT_DEP_CRYPTO_LIBS)
+
+if USE_COMPOSEFS
+libotcore_la_LIBADD += $(OT_DEP_COMPOSEFS_LIBS)
+endif
\ No newline at end of file
#include "config.h"
#include "otcore.h"
+#include <errno.h>
+#include <ostree-core.h>
+#include <ostree-repo-private.h>
+
+#ifdef HAVE_COMPOSEFS
+#include <libcomposefs/lcfs-mount.h>
+#include <libcomposefs/lcfs-writer.h>
+#endif
// This key is used by default if present in the initramfs to verify
// the signature on the target commit object. When composefs is
return g_steal_pointer (&ret);
}
+
+#ifdef HAVE_COMPOSEFS
+static GVariant *
+load_variant (const char *root_mountpoint, const char *digest, const char *extension,
+ const GVariantType *type, GError **error)
+{
+ g_autofree char *path = g_strdup_printf ("%s/ostree/repo/objects/%.2s/%s.%s", root_mountpoint,
+ digest, digest + 2, extension);
+
+ char *data = NULL;
+ gsize data_size;
+ if (!g_file_get_contents (path, &data, &data_size, error))
+ return NULL;
+
+ return g_variant_ref_sink (g_variant_new_from_data (type, data, data_size, FALSE, g_free, data));
+}
+
+// Given a mount point, directly load the .commit object. At the current time this tool
+// doesn't link to libostree.
+static gboolean
+load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GVariant **commit_out,
+ GVariant **commitmeta_out, GError **error)
+{
+ g_autoptr (GError) local_error = NULL;
+ g_autofree char *digest = g_path_get_basename (deploy_path);
+ char *dot = strchr (digest, '.');
+ if (dot != NULL)
+ *dot = 0;
+
+ g_autoptr (GVariant) commit_v
+ = load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, error);
+ if (commit_v == NULL)
+ return FALSE;
+
+ g_autoptr (GVariant) commitmeta_v = load_variant (root_mountpoint, digest, "commitmeta",
+ G_VARIANT_TYPE ("a{sv}"), &local_error);
+ if (commitmeta_v == NULL)
+ {
+ if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ glnx_throw (error, "No commitmeta for commit %s", digest);
+ else
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ return FALSE;
+ }
+
+ *commit_out = g_steal_pointer (&commit_v);
+ *commitmeta_out = g_steal_pointer (&commitmeta_v);
+
+ return TRUE;
+}
+
+/**
+ * validate_signature:
+ * @data: The raw data whose signature must be validated
+ * @signatures: A variant of type "ay" (byte array) containing signatures
+ * @pubkeys: an array of type GBytes*
+ *
+ * Verify that @data is signed using @signatures and @pubkeys.
+ */
+static gboolean
+validate_signature (GBytes *data, GVariant *signatures, GPtrArray *pubkeys, GError **error)
+{
+ g_assert (data);
+ g_assert (signatures);
+ g_assert (pubkeys);
+
+ for (gsize j = 0; j < pubkeys->len; j++)
+ {
+ GBytes *pubkey = pubkeys->pdata[j];
+ g_assert (pubkey);
+
+ for (gsize i = 0; i < g_variant_n_children (signatures); i++)
+ {
+ g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
+ g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
+ bool valid = false;
+
+ if (!otcore_validate_ed25519_signature (data, pubkey, signature, &valid, error))
+ return glnx_prefix_error (error, "signature verification failed");
+ // At least one valid signature is enough.
+ if (valid)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+// Output a friendly message based on an errno for common cases
+static const char *
+composefs_error_message (int errsv)
+{
+ switch (errsv)
+ {
+ case ENOVERITY:
+ return "fsverity not enabled on composefs image";
+ case EWRONGVERITY:
+ return "Wrong fsverity digest in composefs image";
+ case ENOSIGNATURE:
+ return "Missing signature for fsverity in composefs image";
+ default:
+ return strerror (errsv);
+ }
+}
+
+#endif
+
+gboolean
+otcore_mount_composefs (ComposefsConfig *composefs_config, GVariantBuilder *metadata_builder,
+ gboolean root_transient, const char *root_mountpoint,
+ const char *deploy_path, const char *mount_target,
+ bool *out_using_composefs, GError **error)
+{
+ bool using_composefs = FALSE;
+#ifdef HAVE_COMPOSEFS
+ /* We construct the new sysroot in /sysroot.tmp, which is either the composefs
+ mount or a bind mount of the deploy-dir */
+ if (composefs_config->enabled == OT_TRISTATE_NO)
+ return TRUE;
+
+ const char *objdirs[] = { "/sysroot/ostree/repo/objects" };
+ struct lcfs_mount_options_s cfs_options = {
+ objdirs,
+ 1,
+ };
+
+ cfs_options.flags = 0;
+ cfs_options.image_mountdir = OSTREE_COMPOSEFS_LOWERMNT;
+ if (mkdirat (AT_FDCWD, OSTREE_COMPOSEFS_LOWERMNT, 0700) < 0 && errno != EEXIST)
+ return glnx_throw (error, "Failed to create %s", OSTREE_COMPOSEFS_LOWERMNT);
+
+ g_autofree char *expected_digest = NULL;
+
+ // For now we just stick the transient root on the default /run tmpfs;
+ // however, see
+ // https://github.com/systemd/systemd/blob/604b2001081adcbd64ee1fbe7de7a6d77c5209fe/src/basic/mountpoint-util.h#L36
+ // which bumps up these defaults for the rootfs a bit.
+ g_autofree char *root_upperdir
+ = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/upper", NULL) : NULL;
+ g_autofree char *root_workdir
+ = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/work", NULL) : NULL;
+
+ // Propagate these options for transient root, if provided
+ if (root_transient)
+ {
+ if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_upperdir, 0755, NULL, error))
+ return glnx_prefix_error (error, "Failed to create %s", root_upperdir);
+ if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_workdir, 0700, NULL, error))
+ return glnx_prefix_error (error, "Failed to create %s", root_workdir);
+
+ cfs_options.workdir = root_workdir;
+ cfs_options.upperdir = root_upperdir;
+ }
+ else
+ {
+ cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY;
+ }
+
+ if (composefs_config->is_signed)
+ {
+ const char *composefs_pubkey = composefs_config->signature_pubkey;
+ g_autoptr (GVariant) commit = NULL;
+ g_autoptr (GVariant) commitmeta = NULL;
+
+ if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta, error))
+ return glnx_prefix_error (error, "Error loading signatures from repo");
+
+ g_autoptr (GVariant) signatures = g_variant_lookup_value (
+ commitmeta, OSTREE_SIGN_METADATA_ED25519_KEY, G_VARIANT_TYPE ("aay"));
+ if (signatures == NULL)
+ return glnx_throw (error, "Signature validation requested, but no signatures in commit");
+
+ g_autoptr (GBytes) commit_data = g_variant_get_data_as_bytes (commit);
+ if (!validate_signature (commit_data, signatures, composefs_config->pubkeys, error))
+ return glnx_throw (error, "No valid signatures found for public key");
+
+ g_print ("composefs+ostree: Validated commit signature using '%s'\n", composefs_pubkey);
+ g_variant_builder_add (metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS_SIGNATURE,
+ g_variant_new_string (composefs_pubkey));
+
+ g_autoptr (GVariant) metadata = g_variant_get_child_value (commit, 0);
+ g_autoptr (GVariant) cfs_digest_v = g_variant_lookup_value (
+ metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING);
+ if (cfs_digest_v == NULL || g_variant_get_size (cfs_digest_v) != OSTREE_SHA256_DIGEST_LEN)
+ return glnx_throw (error, "Signature validation requested, but no valid digest in commit");
+ const guint8 *cfs_digest_buf = ot_variant_get_data (cfs_digest_v, error);
+ if (!cfs_digest_buf)
+ return glnx_prefix_error (error, "Failed to query digest");
+
+ expected_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1);
+ ot_bin2hex (expected_digest, cfs_digest_buf, g_variant_get_size (cfs_digest_v));
+
+ g_assert (composefs_config->require_verity);
+ cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
+ g_print ("composefs: Verifying digest: %s\n", expected_digest);
+ cfs_options.expected_fsverity_digest = expected_digest;
+ }
+ else if (composefs_config->require_verity)
+ {
+ cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
+ }
+
+ if (lcfs_mount_image (OSTREE_COMPOSEFS_NAME, mount_target, &cfs_options) == 0)
+ {
+ using_composefs = true;
+ bool using_verity = (cfs_options.flags & LCFS_MOUNT_FLAGS_REQUIRE_VERITY) > 0;
+ g_variant_builder_add (metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS,
+ g_variant_new_boolean (true));
+ g_variant_builder_add (metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS_VERITY,
+ g_variant_new_boolean (using_verity));
+ g_print ("composefs: mounted successfully (verity=%s)\n", using_verity ? "true" : "false");
+ }
+ else
+ {
+ int errsv = errno;
+ g_assert (composefs_config->enabled != OT_TRISTATE_NO);
+ if (composefs_config->enabled == OT_TRISTATE_MAYBE && errsv == ENOENT)
+ {
+ g_print ("composefs: No image present\n");
+ }
+ else
+ {
+ const char *errmsg = composefs_error_message (errsv);
+ return glnx_throw (error, "composefs: failed to mount: %s", errmsg);
+ }
+ }
+#else
+ /* if composefs is configured as "maybe", we should continue */
+ if (composefs_config->enabled == OT_TRISTATE_YES)
+ return glnx_throw (error, "composefs: enabled at runtime, but support is not compiled in");
+#endif
+ *out_using_composefs = using_composefs;
+ return TRUE;
+}
ComposefsConfig *otcore_load_composefs_config (const char *cmdline, GKeyFile *config,
gboolean load_keys, GError **error);
+/**
+ * otcore_mount_composefs:
+ * @composefs_config: Configuration for composefs.
+ * @metadata_builder: (transfer none): GVariantBuilder to add metadata to.
+ * @root_transient: Whether the root filesystem is transient.
+ * @root_mountpoint: The mount point of the physical root filesystem.
+ * @deploy_path: The path to the deployment.
+ * @mount_target: The target path to mount the composefs image.
+ * @out_using_composefs: (out): Whether composefs was successfully used.
+ * @error: (out): Return location for a GError, or %NULL.
+ *
+ * Mounts a composefs image based on the provided configuration.
+ *
+ * Returns: %TRUE on success, %FALSE on error.
+ */
+gboolean otcore_mount_composefs (ComposefsConfig *composefs_config,
+ GVariantBuilder *metadata_builder, gboolean root_transient,
+ const char *root_mountpoint, const char *deploy_path,
+ const char *mount_target, bool *out_using_composefs,
+ GError **error);
+
// Our directory with transient state (eventually /run/ostree-booted should be a link to
// /run/ostree/booted)
#define OTCORE_RUN_OSTREE "/run/ostree"
// A temporary mount point
#define TMP_SYSROOT "/sysroot.tmp"
-#ifdef HAVE_COMPOSEFS
-#include <libcomposefs/lcfs-mount.h>
-#include <libcomposefs/lcfs-writer.h>
-#endif
-
#include "ostree-mount-util.h"
static GOptionEntry options[] = { { NULL } };
return deploy_path;
}
-#ifdef HAVE_COMPOSEFS
-static GVariant *
-load_variant (const char *root_mountpoint, const char *digest, const char *extension,
- const GVariantType *type, GError **error)
-{
- g_autofree char *path = g_strdup_printf ("%s/ostree/repo/objects/%.2s/%s.%s", root_mountpoint,
- digest, digest + 2, extension);
-
- char *data = NULL;
- gsize data_size;
- if (!g_file_get_contents (path, &data, &data_size, error))
- return NULL;
-
- return g_variant_ref_sink (g_variant_new_from_data (type, data, data_size, FALSE, g_free, data));
-}
-
-// Given a mount point, directly load the .commit object. At the current time this tool
-// doesn't link to libostree.
-static gboolean
-load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GVariant **commit_out,
- GVariant **commitmeta_out, GError **error)
-{
- g_autoptr (GError) local_error = NULL;
- g_autofree char *digest = g_path_get_basename (deploy_path);
- char *dot = strchr (digest, '.');
- if (dot != NULL)
- *dot = 0;
-
- g_autoptr (GVariant) commit_v
- = load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, error);
- if (commit_v == NULL)
- return FALSE;
-
- g_autoptr (GVariant) commitmeta_v = load_variant (root_mountpoint, digest, "commitmeta",
- G_VARIANT_TYPE ("a{sv}"), &local_error);
- if (commitmeta_v == NULL)
- {
- if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
- glnx_throw (error, "No commitmeta for commit %s", digest);
- else
- g_propagate_error (error, g_steal_pointer (&local_error));
- return FALSE;
- }
-
- *commit_out = g_steal_pointer (&commit_v);
- *commitmeta_out = g_steal_pointer (&commitmeta_v);
-
- return TRUE;
-}
-
-/**
- * validate_signature:
- * @data: The raw data whose signature must be validated
- * @signatures: A variant of type "ay" (byte array) containing signatures
- * @pubkeys: an array of type GBytes*
- *
- * Verify that @data is signed using @signatures and @pubkeys.
- */
-static gboolean
-validate_signature (GBytes *data, GVariant *signatures, GPtrArray *pubkeys)
-{
- g_assert (data);
- g_assert (signatures);
- g_assert (pubkeys);
-
- for (gsize j = 0; j < pubkeys->len; j++)
- {
- GBytes *pubkey = pubkeys->pdata[j];
- g_assert (pubkey);
-
- for (gsize i = 0; i < g_variant_n_children (signatures); i++)
- {
- g_autoptr (GError) local_error = NULL;
- g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
- g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
- bool valid = false;
-
- if (!otcore_validate_ed25519_signature (data, pubkey, signature, &valid, &local_error))
- errx (EXIT_FAILURE, "signature verification failed: %s", local_error->message);
- // At least one valid signature is enough.
- if (valid)
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
-// Output a friendly message based on an errno for common cases
-static const char *
-composefs_error_message (int errsv)
-{
- switch (errsv)
- {
- case ENOVERITY:
- return "fsverity not enabled on composefs image";
- case EWRONGVERITY:
- return "Wrong fsverity digest in composefs image";
- case ENOSIGNATURE:
- return "Missing signature for fsverity in composefs image";
- default:
- return strerror (errsv);
- }
-}
-
-#endif
-
int
main (int argc, char *argv[])
{
// Tracks if we did successfully enable it at runtime
bool using_composefs = false;
-
-#ifdef HAVE_COMPOSEFS
- /* We construct the new sysroot in /sysroot.tmp, which is either the composefs
- mount or a bind mount of the deploy-dir */
- if (composefs_config->enabled != OT_TRISTATE_NO)
- {
- const char *objdirs[] = { "/sysroot/ostree/repo/objects" };
- g_autofree char *cfs_digest = NULL;
- struct lcfs_mount_options_s cfs_options = {
- objdirs,
- 1,
- };
-
- cfs_options.flags = 0;
- cfs_options.image_mountdir = OSTREE_COMPOSEFS_LOWERMNT;
- if (mkdirat (AT_FDCWD, OSTREE_COMPOSEFS_LOWERMNT, 0700) < 0 && errno != EEXIST)
- err (EXIT_FAILURE, "Failed to create %s", OSTREE_COMPOSEFS_LOWERMNT);
-
- g_autofree char *expected_digest = NULL;
-
- // For now we just stick the transient root on the default /run tmpfs;
- // however, see
- // https://github.com/systemd/systemd/blob/604b2001081adcbd64ee1fbe7de7a6d77c5209fe/src/basic/mountpoint-util.h#L36
- // which bumps up these defaults for the rootfs a bit.
- g_autofree char *root_upperdir
- = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/upper", NULL)
- : NULL;
- g_autofree char *root_workdir
- = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/work", NULL) : NULL;
-
- // Propagate these options for transient root, if provided
- if (root_transient)
- {
- if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_upperdir, 0755, NULL, &error))
- errx (EXIT_FAILURE, "Failed to create %s: %s", root_upperdir, error->message);
- if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_workdir, 0700, NULL, &error))
- errx (EXIT_FAILURE, "Failed to create %s: %s", root_workdir, error->message);
-
- cfs_options.workdir = root_workdir;
- cfs_options.upperdir = root_upperdir;
- }
- else
- {
- cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY;
- }
-
- if (composefs_config->is_signed)
- {
- const char *composefs_pubkey = composefs_config->signature_pubkey;
- g_autoptr (GError) local_error = NULL;
- g_autoptr (GVariant) commit = NULL;
- g_autoptr (GVariant) commitmeta = NULL;
-
- if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta,
- &local_error))
- errx (EXIT_FAILURE, "Error loading signatures from repo: %s", local_error->message);
-
- g_autoptr (GVariant) signatures = g_variant_lookup_value (
- commitmeta, OSTREE_SIGN_METADATA_ED25519_KEY, G_VARIANT_TYPE ("aay"));
- if (signatures == NULL)
- errx (EXIT_FAILURE, "Signature validation requested, but no signatures in commit");
-
- g_autoptr (GBytes) commit_data = g_variant_get_data_as_bytes (commit);
- if (!validate_signature (commit_data, signatures, composefs_config->pubkeys))
- errx (EXIT_FAILURE, "No valid signatures found for public key");
-
- g_print ("composefs+ostree: Validated commit signature using '%s'\n", composefs_pubkey);
- g_variant_builder_add (&metadata_builder, "{sv}",
- OTCORE_RUN_BOOTED_KEY_COMPOSEFS_SIGNATURE,
- g_variant_new_string (composefs_pubkey));
-
- g_autoptr (GVariant) metadata = g_variant_get_child_value (commit, 0);
- g_autoptr (GVariant) cfs_digest_v = g_variant_lookup_value (
- metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING);
- if (cfs_digest_v == NULL || g_variant_get_size (cfs_digest_v) != OSTREE_SHA256_DIGEST_LEN)
- errx (EXIT_FAILURE, "Signature validation requested, but no valid digest in commit");
- const guint8 *cfs_digest_buf = ot_variant_get_data (cfs_digest_v, &error);
- if (!cfs_digest_buf)
- errx (EXIT_FAILURE, "Failed to query digest: %s", error->message);
-
- expected_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1);
- ot_bin2hex (expected_digest, cfs_digest_buf, g_variant_get_size (cfs_digest_v));
-
- g_assert (composefs_config->require_verity);
- cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
- g_print ("composefs: Verifying digest: %s\n", expected_digest);
- cfs_options.expected_fsverity_digest = expected_digest;
- }
- else if (composefs_config->require_verity)
- {
- cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
- }
-
- if (lcfs_mount_image (OSTREE_COMPOSEFS_NAME, TMP_SYSROOT, &cfs_options) == 0)
- {
- using_composefs = true;
- bool using_verity = (cfs_options.flags & LCFS_MOUNT_FLAGS_REQUIRE_VERITY) > 0;
- g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS,
- g_variant_new_boolean (true));
- g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS_VERITY,
- g_variant_new_boolean (using_verity));
- g_print ("composefs: mounted successfully (verity=%s)\n",
- using_verity ? "true" : "false");
- }
- else
- {
- int errsv = errno;
- g_assert (composefs_config->enabled != OT_TRISTATE_NO);
- if (composefs_config->enabled == OT_TRISTATE_MAYBE && errsv == ENOENT)
- {
- g_print ("composefs: No image present\n");
- }
- else
- {
- const char *errmsg = composefs_error_message (errsv);
- errx (EXIT_FAILURE, "composefs: failed to mount: %s", errmsg);
- }
- }
- }
-#else
- /* if composefs is configured as "maybe", we should continue */
- if (composefs_config->enabled == OT_TRISTATE_YES)
- errx (EXIT_FAILURE, "composefs: enabled at runtime, but support is not compiled in");
-#endif
+ if (!otcore_mount_composefs (composefs_config, &metadata_builder, root_transient, root_mountpoint,
+ deploy_path, TMP_SYSROOT, &using_composefs, &error))
+ errx (EXIT_FAILURE, "Failed to mount composefs: %s", error->message);
if (!using_composefs)
{